﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using SCGILite.Helper;

namespace SCGILite
{
    public class SCgiRequest : SCgiStream
    {
        public SCgiRequest(Action OnHead, Action<byte[], int, int> OnBodyData, Action OnBody)
        {
            _OnHead = OnHead;
            _OnBody = OnBody;
            _OnBodyData = OnBodyData;
        }
        Action _OnBody;
        Action _OnHead;
        Action<byte[], int, int> _OnBodyData;
        MemoryStream headStream = new MemoryStream();

        #region Header Desc
        /*
        CONTENT_LENGTH, 0
	    REQUEST_METHOD, GET	
        REQUEST_URI, /scgi
        QUERY_STRING, 	
        CONTENT_TYPE, 	
	    DOCUMENT_URI, /scgi	
	    DOCUMENT_ROOT, E:\wuda\nginx-1.1.11/html
	    SCGI, 1	
	    SERVER_PROTOCOL, HTTP/1.1
	    REMOTE_ADDR, 127.0.0.1	
	    REMOTE_PORT, 11069	
	    SERVER_PORT, 80	
	    SERVER_NAME, localhost
	    HTTP_HOST, localhost
	    HTTP_CONNECTION, keep-alive
	    HTTP_CACHE_CONTROL, max-age=0]
	    HTTP_USER_AGENT, Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.47 Safari/536.11]}	
	    HTTP_ACCEPT, text/html,application/xhtml+xml,application/xml;q=0.9,*/
        /*;q=0.8]}	
        HTTP_ACCEPT_ENCODING, gzip,deflate,sdch]}	
        HTTP_ACCEPT_LANGUAGE, zh-CN,zh;q=0.8]}	
        */
        #endregion

        #region ProcessHeaders
        void ProcessHeaders(Dictionary<string, string> h)
        {
            Exception ee = new Exception("Protocol err!!");
            try
            {
                _ProcessHeaders(h, ee);
            }
            catch
            {
                throw ee;
            }
        }
        void _ProcessHeaders(Dictionary<string, string> h, Exception e)
        {
            string v1 = "";
            v1 = h["SCGI"];
            if (v1 != "1")
                throw e;

            v1 = h["CONTENT_LENGTH"];
            this.bodyLength = int.Parse(v1);

            _REQUEST_METHOD = h["REQUEST_METHOD"];
            if ((_REQUEST_METHOD != "POST") && (_REQUEST_METHOD != "GET"))
                throw e;
            _REQUEST_URI = h["REQUEST_URI"];
        }
        #endregion

        #region DecodeHeaders
        protected Dictionary<string, string> DecodeHeaders(byte[] HeaderData, int len)
        {
            if ((HeaderData[0] == (byte)0) || (HeaderData[len - 2] != (byte)0) || (HeaderData[len - 1] != (byte)','))
                throw errException;
            var Headers = new Dictionary<string, string>();
            Queue<int> lst = new Queue<int>();
            lst.Enqueue(0);
            for (int i = 0; i < len; i++)
            {
                if (HeaderData[i] == (byte)0)
                    lst.Enqueue(i + 1);
            }
            if ((lst.Count & 0x01) != 0x01)
                throw new Exception("Header Parse err!!");
            while(lst.Count > 2)
            {
                int l1 = lst.Dequeue();
                int l2 = lst.Dequeue();
                int l3 = lst.Peek();
                string k = Encoding.UTF8.GetString(HeaderData, l1, l2 - l1 - 1);
                string v = Encoding.UTF8.GetString(HeaderData, l2, l3 - l2 - 1);
                Headers[k] = v;
            }
            return Headers;
        }
        #endregion

        #region Write
        public override void Write(byte[] buffer, int offset, int count)
        {
            if (count <= 0)
                throw new Exception("Read Request head err");
            DataFsm(buffer, offset, count);
        }
        #endregion

        int fsm = 0;
        int HeaderLength = 0;

        string _REQUEST_METHOD = "GET";
        public string REQUEST_METHOD { get { return _REQUEST_METHOD; } }
        string _REQUEST_URI;
        public string REQUEST_URI { get { return _REQUEST_URI; } }
        int bodyLength = 0;
        int bodyStreamLength = 0;
        public Dictionary<string, string> Headers { get; private set; }

        #region DataFsm
        void WriteHead(byte[] buf, int offset, int len)
        {
            if (len == 0) return;
            headStream.Write(buf, offset, len);
        }
        void WriteBody(byte[] buf, int offset, int len)
        {
            if (len == 0) return;
            bodyStreamLength += len;
            _OnBodyData(buf, offset, len);
        }
        void clrMemoryStreamHeader(MemoryStream ms, int offset)
        {
            byte[] buf = ms.GetBuffer();
            for (int i = 0; i < ms.Length - offset; i++)
            {
                buf[i] = buf[i + offset];
            }
            ms.SetLength(ms.Length - offset);
        }
        void DataFsm()
        {
            DataFsm(null, 0, 0);
        }

        static readonly Exception errException = new Exception("Invalid Scgi request");
        void DataFsm(byte[] buffer, int offset, int count)
        {
            if (fsm == 0)
            {
                WriteHead(buffer, offset, count);
                byte[] buf = headStream.GetBuffer();
                for (int i = 0; i < headStream.Length; i++)
                {
                    if (buf[i] == (byte)':')
                    {
                        HeaderLength = int.Parse(Encoding.UTF8.GetString(buf, 0, i));
                        if (HeaderLength > (1024 * 10))
                            throw errException;
                        clrMemoryStreamHeader(headStream, i + 1);
                        fsm = 1;
                        DataFsm();
                        return;
                    }
                }
                if (headStream.Length > 20)
                    throw errException;
            }
            else if (fsm == 1)//Header
            {
                WriteHead(buffer, offset, count);
                if (headStream.Length < (HeaderLength + 1))
                    return;
                byte[] buf = headStream.GetBuffer();
                if (buf[HeaderLength] != (byte)',')
                    throw errException;
                Headers = DecodeHeaders(buf, HeaderLength + 1);
                ProcessHeaders(Headers);
                _OnHead();
                WriteBody(buf, HeaderLength + 1, (int)headStream.Length - HeaderLength - 1);
                headStream.Close();
                fsm = 2;
                DataFsm();
            }
            else if (fsm == 2)//Body
            {
                WriteBody(buffer, offset, count);
                if (bodyStreamLength == this.bodyLength)
                {
                    fsm = 3;
                    _OnBody();
                }
                else if (bodyStreamLength > this.bodyLength)
                    throw errException;
            }
            else
                throw errException;
        }
        #endregion
    }
}
